Baner
; В начало ; Новости ; Теория ; Ресурсы ; Ссылки ; Форум ; Почта ;
Математика и физика
2D графика

   DirectDraw:
3D графика

   OpenGL:
Rambler's Top100 Rambler's Top100
2D Меню.
Зачастую в своих 3D играх возникает надобность создать меню, статистику, панель управления; короче часть экрана на которой должна выводиться 2Dэшная информация. Для 3D шутеров или автосимуляторов это конечно не нужно (хотя... количество фрагов или рекорд круга полезно высвечивать на экране), но то ли дело если речь идет о стратегиях: кнопки управления, количество ресурсов - необходимый аттрибут любой из них. В D3D сделать вывод такого меню, помоему не очень сложно: он с DirectDraw не конфликтует, а вот с OpenGL все немного сложнее. В этой статье и пойдет речь о том как создавать менюхи в OpenGL.

Как это сделать? Вариант номер 1: можно натягивать на Quad текстуру с изображением меню. Вроде просто, но качество будет очень низко, т.к размер тектуры должен быть степенью двойки, а размер меню чаще всего несоответствует размерам тектуры. Начинаются сжимания, разжимания и ничего хорошего от вашей заготовки не останется. Вывод: этот способ неприемлем! Вариант номер 2: рисовать ваше меню в оригинале, без искажений функцией glDrawPixels. О таком способе и пойдет речь.

Все очень просто, если необходимо выводить только информацию о количестве денег, ресурсов, фрагов и т.д, но если должны быть и кнопки, придется немного постараться. Я лениться не буду и расскажу о меню с кнопками. Для начала нужно подготовить нужные изображения:

MenuInstance MenuMask MenuButton
MenuButton
MenuButton
MenuButton
Справа - фон для меню. Посередке - маска кнопок. Справа - сами кнопки, по необходимоти нужно подготовить изображеня нажатых, отжатых и подсвеченных кнопок. Чтобы правильно отбразить кнопки нужно перейти в ортографическую проекцию. Пишем функции соответственно для перехода в орту и обратно:
void CClassGraphic::SetMenuMatrix()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(0, 0, MN_WIDTH, MN_HEIGHT);
	glMatrixMode(GL_MODELVIEW);
}

void CClassGraphic::SetSceneMatrix()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glViewport(MN_WIDTH, 0, RES_W-MN_WIDTH, MN_HEIGHT);
	gluPerspective(45, SCREEN_ASPECT, 1.0f, 30.0f);
	glTranslatef(0,0, -5);
	glMatrixMode(GL_MODELVIEW);
}
Здесь RES_W и RES_H - разрешение экрана. MN_WIDTH и MN_HEIGHT - это соответственно ширина и высота нашего меню. Я использовал меню 200x600. Думаю идея переключения между матрицами понятна. Далее необходимо прорисовать наш шаблон меню. Делаем это, как уже говорилось функцией glDrawPixels. Но есть одна загвоздка. Ни для кого не секрет, что эта функция не отличается быстродействием, поэтому использовать ее просто так - свой труд не уважать. Придется прибегнуть к маленькой хитрости: нарисовать шаблон один раз, а после не трогать его при очистке экрана. Реализовать это можно с помощью OpenGL ножниц (Scissors), которые заставляют OpenGL "забыть" о какой-либо части экрана. Пишем...:
SetMenuMatrix();
glRasterPos2f(-1, -1);
glDrawPixels(MN_WIDTH, MN_HEIGHT, GL_RGB,
	GL_UNSIGNED_BYTE, m_menuTex.m_pData);
glScissor(MN_WIDTH, 0, RES_W-MN_WIDTH, MN_HEIGHT);
glEnable(GL_SCISSOR_TEST);
Главное если вы используйте класс CTexture из раздела "шаблоны" закоментируйте строку, которая после подготовки текстуры удаляет массив пикселей. Далее в соответствии с картой кнопок рисуем сами кнопки. Они маленькие и квадратные, а потому ухудшение качества от наложения текстуры им не грозит.
void CClassGraphic::DrawMenuButton(UINT X1, UINT Y1, UINT X2, UINT Y2)
{
	static const GLfloat scX = 2.0f/MN_WIDTH;
	static const GLfloat scY = 2.0f/MN_HEIGHT;
	
	/*Страшные выражения в скобках glVertex2f -
	  это всего-навсего перевод из оконных координат
	  в координаты OpenGL */
	glBegin(GL_QUADS);
		glTexCoord2f(0.0f,0.0f);
		glVertex2f(scX*X1-1, -scY*Y2+1);

		glTexCoord2f(1.0f,0.0f);
		glVertex2f(scX*X2-1, -scY*Y2+1);

		glTexCoord2f(1.0f,1.0f);
		glVertex2f(scX*X2-1, -scY*Y1+1);

		glTexCoord2f(0.0f,1.0f);
		glVertex2f(scX*X1-1, -scY*Y1+1);
	glEnd();
}

void CClassGraphic::DrawMenu(MENU* pMenu)
{
	SetMenuMatrix();
	
	glLoadIdentity();
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_SCISSOR_TEST);
	glEnable(GL_TEXTURE_2D);

	if (pMenu->ButtonDown[0])	m_bnCubeTex[0].Bind();
	else if (pMenu->MouseOver[0])	m_bnCubeTex[2].Bind();
	else				m_bnCubeTex[1].Bind();
	DrawMenuButton(24, 200, 56, 232);

	if (pMenu->ButtonDown[1])	m_bnSphereTex[0].Bind();
	else if (pMenu->MouseOver[1])	m_bnSphereTex[2].Bind();
	else				m_bnSphereTex[1].Bind();
	DrawMenuButton(124, 200, 156, 232);

	if (pMenu->ButtonDown[2])	m_bnCylinderTex[0].Bind();
	else if (pMenu->MouseOver[2])	m_bnCylinderTex[2].Bind(); 
	else				m_bnCylinderTex[1].Bind();
	DrawMenuButton(24, 250, 56, 282);

	if (pMenu->ButtonDown[3])	m_bnConeTex[0].Bind();
	else if (pMenu->MouseOver[3])	m_bnConeTex[2].Bind();
	else				m_bnConeTex[1].Bind();
	DrawMenuButton(124, 250, 156, 282);

	glDisable(GL_TEXTURE_2D);
	glEnable(GL_SCISSOR_TEST);
	glEnable(GL_DEPTH_TEST);
}
В структуре MENU переменная MouseOver показывает находится ли курсор над кнопкой, а переменная MouseDown показывает зажата ли кнопка или нет. Теперь все нарисованное необходимо связать с перемещениями и щелканиями мыши:
if (mouseX<MN_WIDTH)
{
const unsigned  char r =
	m_menuMask.m_pData[3*(MN_WIDTH*(MN_HEIGHT-mouseY)+mouseX)+ 0];
const unsigned  char g =
	m_menuMask.m_pData[3*(MN_WIDTH*(MN_HEIGHT-mouseY)+mouseX)+ 1];
const unsigned  char b =
	m_menuMask.m_pData[3*(MN_WIDTH*(MN_HEIGHT-mouseY)+mouseX)+ 2];

int clid = -1;
if (r==255 && g==0   && b==0  ) clid=0; else
if (r==0   && g==255 && b==0  ) clid=1; else
if (r==0   && g==0   && b==255) clid=2; else
if (r==255 && g==255 && b==0  ) clid=3;

m_scene.Menu.MouseOver[0] = (clid==0)? TRUE : FALSE;
m_scene.Menu.MouseOver[1] = (clid==1)? TRUE : FALSE;
m_scene.Menu.MouseOver[2] = (clid==2)? TRUE : FALSE;
m_scene.Menu.MouseOver[3] = (clid==3)? TRUE : FALSE;

if (LClick && clid != -1)
	for (UINT i=0; i<4; i++)
	{
		if (clid==(int)i)
		{
			m_scene.Menu.ButtonDown[i] = TRUE;
			m_scene.Obj = i+1;
		/*Тут можете вставлять свои реакции на нажатия кнопок
		  переменная clid содержит идентификатор щелкнутой
		  кнопки. */
		}
		else
			m_scene.Menu.ButtonDown[i] = FALSE;
	}		
}
Переменные mouseX и moseY - это, как вы, наверное, догадались координаты мыши; переменная LClick показывает нажата ли левая кнопа мыши. Как видно, идентификатор кнопки определяется по цвету соответствующего пикселя в карте кнопок. В больших меню разумнее использовать готовые файлы устанавливающие соответствие кнопок и цветов, но я даю только идею - развивать вы ее можете по своему усмотрению. Вот собственно и все. Если что непонятно посмотрите в исходники демки или напишите.

Примечание №1: Если вы хотите использовать непрямоугольные меню, например как в Sudden Strike рисуйте их в несколько приемов, разделив на несколько прямоугольников, или используйте текстуры с Alpha каналом.
Примечание №2: Если вы не хотите иметь фон, вроде как в OpenGL танчиках не применяйте glDrawPixels, а сразу рисуйте изображения с Alpha каналом.


MenuDemo И снова демка. Меню от Junk Studio - не очень красиво, зато нахаляву.

Cкачать демку.[52 Kb]
Cкачать исходники.[68 Kb]

Используются технологии uCoz